
package gomz.server;

import gomz.res.Message;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;


/**
 * @author Schatzmann
 * Diese Klasse wird verwendet um auf dem Server einen ServerSocket zu erzeugen welcher
 * Verbindungen entgegennimmt und pro Verbindung dann einen ServerToClientCon Thread startet
 * Initialisiert ausserdem den Logger
 */
public class MServer implements ThreadFinishedListener{
    Thread serverThread;
// ********************************************************************************
// Variablen
// ********************************************************************************
    private ServerSocket serverSocket;
    private Socket serverClient;
    private int port;
    private String dbUser;
    private String dbPW;
    //speichere alle Client Threads in einer ArrayList
    private ArrayList<ServerToClientCon> clientCons = new ArrayList<ServerToClientCon>();
    //listener hinzufügen
    private ThreadFinishedListener threadListener = this;
    //Maximale Anzahl an Connection = 100
    private final static int numberOfCon = 100;
    //LogListener 
    private ArrayList<LogListener> logListeners = new ArrayList<LogListener>();
   // private Server serverFunctions = new Server(); //TODO delete
    
    public static final String LOGNAME = MServer.class.getSimpleName();
    private Logger log = null;

// ********************************************************************************
// Getter/Setter
// ********************************************************************************
    public Thread getServerThread() {
        return serverThread;
    }

    public void setServerThread(Thread serverThread) {
        this.serverThread = serverThread;
    }

    public ServerSocket getServer() {
        return serverSocket;
    }

    public void setServer(ServerSocket server) {
        this.serverSocket = server;
    }

    public Socket getServerClient() {
        return serverClient;
    }

    public void setServerClient(Socket serverClient) {
        this.serverClient = serverClient;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

// ********************************************************************************
// Konstruktor
// ********************************************************************************
    public MServer(){
        
    }
    
// ********************************************************************************
// Methoden
// ********************************************************************************
    /**
     * Konfiguriere den ServerSocket und den Listener, starte dann den serverThread
     * @param port Port auf welchem der ServerSocket hört
     * @param dbUser User für den Datenbank zugriff
     * @param dbPW Passwort für den Datenbank zugriff
     * @param exportPath Speicherpad für den Logger Export
     */
    public void configServer(final int port, String dbUser, String dbPW, String exportPath) {
        //Setze Server Variablen
        this.port = port;
        this.dbUser = dbUser;
        this.dbPW = dbPW;      
        //Wenn ExportPfad angegeben ist, dann initialisiere Logger
        if(exportPath != null){
            configLogger(exportPath);     
        } 
        
        this.addLogListener(new LogListener() {		
            @Override
            public void doNotifyTxtDataChange(String logString) {
                    log.info(logString);
            }
        });
        //Starte ServerThread mit dem ServerSocket
        startServerThread();
        //Schreibe in Log das ServerSocket gestartet
        writeLog("Server gestartet");
        writeLog("Listening on port " + port);
      }
    /**
     * Konfiguriere Logger
     * @param exportPath Pfad wo das Log-File gespeichert werden soll
     */
    private void configLogger(String exportPath){
        //Loglevel setzten
    	log = Logger.getLogger(this.getClass().getSimpleName());
        log.setLevel(Level.INFO);
		
        //Defaulthandler hohlen
        Handler[] defHandler = Logger.getLogger("").getHandlers();

        if(defHandler.length == 1){
                //Konsolenhandler ausschalten
                defHandler[0].setLevel(Level.OFF);
        }

        //Filehandler hohlen
        try{
                Handler logHandler = new FileHandler(exportPath);
                logHandler.setLevel(Level.INFO);
                log.addHandler(logHandler);
        }
        catch (Exception e){
            //*******
                writeLog("Logger deaktiviert");
             //********
        }
    }
    
    /**
     * ServerThread welcher den ServerSocket startet
     * ClientVerbindungen entgegen nimmt
     * Die ClientVerbindungen an die ServerToClientCon weitergibt als neuer Thread
     */
    public void startServerThread(){
         //neuer Thread fuer ServerSocket
    	 serverThread = new Thread() {
             /**
              * DEFINITION von serverThread.start() bzw run
              */
             @Override
             public void run() {
             	try {
                     //setze ServerSocket
                     serverSocket = new ServerSocket(port, 10, null);                   
                     //Warte auf Clientanfrage
                     while (true) {
                         //ServerSocket wartet auf Clientanfragen und erstellt daraus einen neuen Socket für den Client
                        Socket client = serverSocket.accept();
                        
                        //maximal 100 connections moeglich
	                if (clientCons.size() < numberOfCon){
                        //erstelle neue ServerToClientCon mit dem erstellten Socket
                        ServerToClientCon clientCon = new ServerToClientCon(client, threadListener, dbUser, dbPW, logListeners);
                        //starte den Thread
                        clientCon.start();
                        //Speichere den Thread im clientCons Array
                        clientCons.add(clientCon);
                         }
                        //falls bereits 100 Client verbunden, lehne client ab
                        else{
                            Message msgIn = Message.receive(client);
                            Message msgOut = new Message(Message.ActionTyp.OutOfConnections);
                            msgOut.setObjectTyp(msgIn.getObjectTyp());
                            msgOut.send(client);
                        }
                     }
                 }
                 catch(Exception e){
                    //*******
                     writeLog("Server gestoppt");
                     writeLog("********");
                   //********
                 }
             }
     
     
         };
         //Starte ServerThread
         serverThread.start();
    }
    
    /**
     * Methode um den Server zu beenden
     * beendet zuerst alle einzelnen Verbindungen
     * und dann den ServerSocket bzw. Thread
     */
    public void endServer(){
        //beende alle einzelnen Client Verbindungen
    	for(ServerToClientCon con : clientCons){
                con.getThreadListener().notifyConnectionFinished(con);    
    	}
        //schliesse den ServerSocket
        try{
            serverSocket.close();
        }
        catch(Exception e){
            //*******
             writeLog("Server konnte nicht gestoppt werden");
             writeLog("Fehlermeldung: " + e);
           //********
        }
        //beende den ServerThread
        serverThread.interrupt();      
    }
    

    /**
     * Fuege LogListener zur Liste der LogListener hinzu
     * Liste verwaltet alle LogListener aller Threads
     * @param logListener 
     */
    public void addLogListener(LogListener logListener){
    	this.logListeners.add(logListener);
    }
    
    /**
     * Fuege etwas in den Log hinzu
     * @param logString String welcher dem log hinzugefuegt werden soll
     */
    public void writeLog(String logString){
    	for (LogListener ll : logListeners){
    		ll.doNotifyTxtDataChange(logString);
    	}
    }
    /**
     * Beende eine Connection zu einem Client
     * @param con Verbidung zu Client
     */
    @Override
    public void notifyConnectionFinished(ServerToClientCon con) {
                //Schliesse socket
                try{
                    con.getClient().close();
                }
                catch(Exception e){
                    writeLog("ClientSocket konnte nicht gestoppt werden");
                    writeLog("Fehlermeldung: " + e);
                }
            //unterbreche Thread
            con.interrupt();
            //entferne Thread aus der Liste
            clientCons.remove(con);
    }
}
